﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Chat
{
    public class Message
    {
        public DateTime Time { get; private set; }
        public MessageType Type { get; private set; }
        public string[] Data { get; private set; }

        public Message(DateTime time, MessageType type, string[] data)
        {
            this.Time = time;
            this.Type = type;
            this.Data = data;
        }

        public byte[] ToPacketBytes()
        {
            byte[] timeBytes = BitConverter.GetBytes(Time.ToFileTime());
            byte[] typeBytes = new byte[1];
            typeBytes[0] = (byte)this.Type;

            byte[] numberOfDataEntries = BitConverter.GetBytes(this.Data.Length);

            MemoryStream dataByteWriter = new MemoryStream();

            int dataByteCounter = 0;

            foreach( string dataString in this.Data )
            {
                byte[] dataStringLengthBytes = BitConverter.GetBytes(dataString.Length);
                byte[] dataStringBytes = Encoding.UTF8.GetBytes(dataString);

                dataByteWriter.Write(dataStringLengthBytes, 0, dataStringLengthBytes.Length);
                dataByteWriter.Write(dataStringBytes, 0, dataStringBytes.Length);

                dataByteCounter += dataStringLengthBytes.Length;
                dataByteCounter += dataStringBytes.Length;
            }

            byte[] dataBytes = dataByteWriter.GetBuffer();

            int totalBytes = timeBytes.Length + typeBytes.Length + numberOfDataEntries.Length + dataByteCounter + 4;

            byte[] packetSizeBytes = BitConverter.GetBytes(totalBytes);
            
            MemoryStream stream = new MemoryStream();

            stream.Write(packetSizeBytes, 0, packetSizeBytes.Length);
            stream.Write(timeBytes, 0, timeBytes.Length);
            stream.Write(typeBytes, 0, typeBytes.Length);
            stream.Write(numberOfDataEntries, 0, numberOfDataEntries.Length);
            stream.Write(dataBytes, 0, dataByteCounter);

            byte[] packetBuffer = new byte[totalBytes];

            Array.Copy(stream.GetBuffer(), packetBuffer, packetBuffer.Length);

            if (totalBytes != packetBuffer.Length)
                throw new InvalidOperationException("Sending Invalid Packet Length");

            return packetBuffer;
        }

        public static Message FromPacketBytes(byte[] bytes)
        {
            MemoryStream byteStream = new MemoryStream(bytes);

            byte[] packetSizeBytes = new byte[4];
            byte[] timeBytes = new byte[8];
            byte[] typeBytes = new byte[1];
            byte[] numberOfDataEntriesBytes = new byte[4];

            int dataByteSize = bytes.Length - (packetSizeBytes.Length + timeBytes.Length + typeBytes.Length + numberOfDataEntriesBytes.Length);
            byte[] dataBytes = new byte[dataByteSize];
            
            byteStream.Read(packetSizeBytes, 0, packetSizeBytes.Length);
            byteStream.Read(timeBytes, 0, timeBytes.Length);
            byteStream.Read(typeBytes, 0, typeBytes.Length);
            byteStream.Read(numberOfDataEntriesBytes, 0, numberOfDataEntriesBytes.Length);
            byteStream.Read(dataBytes, 0, dataBytes.Length);
            
            int packetSize = BitConverter.ToInt32(packetSizeBytes, 0);

            if (packetSize != bytes.Length)
                throw new InvalidOperationException("Invalid packet size");

            long timeBinary = BitConverter.ToInt64(timeBytes, 0);
            byte typeByte = typeBytes[0];
            int numberOfDataEntries = BitConverter.ToInt32(numberOfDataEntriesBytes, 0);

            List<string> data = new List<string>();

            int byteIndex = 0;

            for (int n = 0; n < numberOfDataEntries; n++)
            {
                int dataStringLength = BitConverter.ToInt32(dataBytes, byteIndex);
                byteIndex += 4;

                string dataString = UTF8Encoding.UTF8.GetString(dataBytes, byteIndex, dataStringLength);
                byteIndex += dataStringLength;

                data.Add(dataString);
            }

            DateTime time = DateTime.FromFileTime(timeBinary);
            MessageType messageType = (MessageType)typeByte;

            return new Message(time, messageType, data.ToArray());
        }
    }
}
